/*
 * Copyright (c) 2013, SRI International
 * All rights reserved.
 * Licensed under the The BSD 3-Clause License;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 * 
 * http://opensource.org/licenses/BSD-3-Clause
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the aic-web-praise nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.sri.ai.lpitools.gwtgui.callback;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
import org.apache.xmlrpc.server.XmlRpcStreamServer;
import org.apache.xmlrpc.webserver.WebServer;

import com.sri.ai.lpitools.gwtgui.server.CallBackListenerInterface;

public class RPCMultiCallBackServerImpl {
	
	private static final Logger logger = Logger.getLogger(RPCMultiCallBackServerImpl.class.getName());
	
	private CallBackServerWrapper server;
	private WebServer webServer = null;
	private boolean isRunning = false;
	private String url;
	private HashMap<String, List<CallBackListenerInterface>> listeners;
		
	public RPCMultiCallBackServerImpl(){
		listeners = new HashMap<String, List<CallBackListenerInterface>>();
	}
	
	/**
	 * Function to start a web server with given port value
	 * @param portValue
	 * @throws XmlRpcException
	 * @throws IOException
	 */
	public String startServer() throws XmlRpcException, IOException{
		
		int portValue = 9000;
		boolean result = false;
		do{
			result = findFreePort(portValue);
			if(!result){
				portValue++;
			}
		}while(!result);
		
		String hostName = getHostName();
		if(hostName.equalsIgnoreCase("")){
			url = "127.0.0.1:" + portValue;
		} else {
			url =  hostName + ":" + portValue;
		}
		
		//if web server is null 
		if(!isRunning){
			synchronized(logger) {
				if(!isRunning) {
					//log that we are attempting to start it
					logger.info("Attempting to start Callback Server on client end...");
					//set up the web server
					webServer = new WebServer(portValue);
				
					XmlRpcStreamServer xss = webServer.getXmlRpcServer();
			        XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl) xss.getConfig();
			        serverConfig.setEnabledForExtensions(true);
			        serverConfig.setContentLengthOptional(true);		
					
			        //initialization of the Execution Engine wrapper
					server = new CallBackServerWrapper(this);
			
			        @SuppressWarnings("unused")
			        // the initialization is important here, compiler warning should be suppressed
			        CallBackServerEndpoint serverEndpoint = new CallBackServerEndpoint(xss, server);
			        
				     //log that we are calling run
			         logger.info("Calling run for CallBack Server on port " + portValue);
				     webServer.start();
				     isRunning = true;
				} else {
					logger.info("Callback Server on client end is running");
				}
			}
		} else {
			logger.info("Callback Server on client end is running");
		}
		return url;
	}
	private String getHostName() {
		try {
			return java.net.InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			logger.error(e.getMessage(), e);
		}
		return "";
	}

	private boolean findFreePort(int port){
	 
		ServerSocket server = null;
		try {
			server = new ServerSocket(port);
			return true;
		} catch (IOException e) {

		} finally{
			try {
				if(server != null){
					server.close();
				}
			} catch (IOException e) {
				logger.warn("unable to close server", e);
			}
		}
		return false;
}
	/**
	 * This function is sued to add a callback listener
	 * @param clientID
	 * @param listener
	 */
	public void addListener(String clientID, CallBackListenerInterface listener){
		logger.debug("adding listener with following client id " +clientID);
		logger.debug("Adding Listener to this RPCMultiCallBackServer " +this);
		if(listeners.get(clientID) != null){
			List<CallBackListenerInterface> currentListener = listeners.get(clientID);
			if(!currentListener.contains(listener)){
				currentListener.add(listener);
			}
		} else {
			List<CallBackListenerInterface> currentListener = new ArrayList<CallBackListenerInterface>();
			currentListener.add(listener);
			listeners.put(clientID, currentListener);
		}
	}
	
	/**
	 * Function to remove a listener when it is no longer needed
	 * @param key
	 * @param listener
	 */
	public void removeListener(String key, Object listener){
		logger.debug("removing listener with following client id " + key);
		if(listeners.containsKey(key)){
			List<CallBackListenerInterface> currentListener = listeners.get(key);
			if(currentListener.contains(listener)){
				currentListener.remove(listener);
				if(currentListener.size() > 0){
					listeners.put(key, currentListener);
				} else {
					listeners.remove(key);
				}
			}
		}		
	}
	/**
	 * Returns the list of listeners that have the given key or null if none uses that key
	 * @param key
	 * @return
	 */
	public List<CallBackListenerInterface> getListeners(String key){
		
		if(listeners.containsKey(key)){
			List<CallBackListenerInterface> currentListener = listeners.get(key);
			return currentListener;
		} else {
			logger.trace("Found no listeners with this RPCMultiCallBackServer " + this);
		}
		return null;		
	}

}
